import React,
{
    useLayoutEffect,
    useRef,
    useState,
    useCallback,
    useEffect,
    forwardRef,
    useImperativeHandle
} from "react"
import PageHead from "../../components/pageHead/index";
import styles from "./index.module.less"
import List from "./list";
import Explain from "./explain";
import G6, { Grid, Graph, IGraph, Arrow, Edge, IEdge } from "./G6"
import * as _ from "lodash"
import {getUId} from "../../utils/tool";
import Icon from "../../components/icon/index";
import undo from "./G6/function/undo";
import redo from "./G6/function/redo";
import registerFactory from 'welabx-g6';

export const monitorContext = React.createContext({})

const { Provider } = monitorContext

export type ItemChild = { label: string, value: number}
export type Item = {
    title: string
    children: Array<ItemChild>
}

const grid = new Grid();

const listData: Array<Item> = [
    {
        title: "机房",
        children: [
            { label: "主机", value: 1 }
        ]
    },
    {
        title: "网络",
        children: [
            { label: "cdn", value: 2 },
            { label: "主域名", value: 3 },
        ]
    },
    {
        title: "前台",
        children: [
            { label: "静态资源", value: 4 },
            { label: "接口响应", value: 5 },
            { label: "js报错", value: 6 },
        ]
    },
    {
        title: "主要页面",
        children: [
            { label: "页面监控", value: 7 }
        ]
    }
]

const listMap = new Map()
listMap.set(1, "主机")
listMap.set(2, "cdn")
listMap.set(3, "主域名")
listMap.set(4, "静态资源")
listMap.set(5, "接口响应")
listMap.set(6, "js报错")
listMap.set(7, "页面监控")

type GraphData = {
    nodes?: Array<any>
    edges?: Array<any>
}

type contextMenuRef = {
    contextMenuDOM: HTMLDivElement
}

const Monitor = () => {
    const contextMenuRef = useRef<contextMenuRef>()
    const containerRef = useRef<HTMLDivElement>()
    const initGraphData = {
        nodes: [],
        edges: []
    }

    const [listValue, setListValue] = useState<number>(undefined)
    const [graphData, setGraphData] = useState<GraphData>(initGraphData)
    const [graph, setGraph] = useState<Graph>(null)

    const contextRight = (event) => {
        event.returnValue = false
        return false
    }

    const menuContextClick = () => {
        const { contextMenuDOM } = contextMenuRef.current
        contextMenuDOM.style.display = "none"
    }

    useEffect(() => {
        containerRef.current.addEventListener("contextmenu", contextRight)
        window.addEventListener("click", menuContextClick)
        return () => {
            containerRef.current.removeEventListener("contextmenu", contextRight)
            window.removeEventListener("click", menuContextClick)
        }
    }, [])

    useLayoutEffect(() => {
        const target = containerRef.current
        const width = target.clientWidth
        const height = target.clientHeight

        const minimap = new G6.Minimap({
            className: styles.monitorMinimap
        });

        const toolbar = new G6.ToolBar({
            container: containerRef.current,
            className: styles.graphToolBar,
            handleClick: (code: string, graph: IGraph) => {
                const zoom = graph.getZoom();
                // console.log(toolbar);

                // code === "undo" && undo(graph)
                // code === "redo" && redo(graph)
                code === "undo" && toolbar.undo()
                code === "redo" && toolbar.redo()

                code === "zoomOut" && graph.zoomTo(1.2 * zoom)
                code === "zoomIn" && graph.zoomTo(0.8 * zoom)
                code === "realZoom" && graph.zoomTo(1)
                code === "autoZoom" && graph.fitView()
            }
        });

        const g = new G6.Graph({
            plugins: [
                grid,
                toolbar,
                minimap
            ],
            container: "container",
            width: width,
            height: height,
            linkCenter: false,
            enabledStack: true,
            modes: {
                default: ['drag-node'],

            },
            nodeStateStyles: {

            }
        });

        g.on('node:mouseenter', (ev) => {
            const node = ev.item;
            g.setItemState(node, 'hover', true);
        });
        g.on('node:mouseleave', (ev) => {
            const node = ev.item;
            g.clearItemStates(node, 'hover')
        });

        g.on('dragnodeend', function(event) {
            // console.log(g.getNodes().map(item => item.getModel().x), "=============");
        })

        g.on('node:dragstart', function(event) {
            // console.log(event, "=============");
            const shape = event.target
        })

        g.on('node:contextmenu', function(event) {
            const item = event.item
            const model = item.getModel()
            const containerOffsetX = containerRef.current.offsetLeft
            const containerOffsetY = containerRef.current.offsetTop
            const x = event.clientX - containerOffsetX + window.scrollX
            const y = event.clientY - containerOffsetY + window.scrollY

            const lastWidth = containerRef.current.clientWidth - x
            const lastHeight = containerRef.current.clientHeight - y

            const { contextMenuDOM } = contextMenuRef.current
            contextMenuDOM.style.top = (lastHeight < 120 ? y - 120 : y) + "px"
            contextMenuDOM.style.left = (lastWidth < 120 ? x - 120 : x) + "px"
            contextMenuDOM.style.display = "block"
        })

        setGraph(g)
    }, [])

    const startNodeRef = useRef<{id: string, point: number}>()
    const endNodeRef = useRef<{id: string, point: number}>()

    const painDrop = async (event: any) => {
        const setNodeStartPoint = (item, point) => {
            startNodeRef.current = { id: item.id, point }
        }
        const setNodeEndPoint = async (item, point) => {
            endNodeRef.current = { id: item.id, point }
            const startNode = startNodeRef.current
            const endNode = endNodeRef.current

            if (startNode.id !== endNode.id) {
                const hasEdge = graph.getEdges().find(item => {
                    const model = item.getModel()
                    if (
                        model.source === startNode.id &&
                        model.target === endNode.id &&
                        model.sourceAnchor === startNode.point &&
                        model.targetAnchor === endNode.point
                    ) {
                        // 有同样的边存在
                        return item
                    }
                })
                if (hasEdge) return

                const edge = {
                    id: getUId(),
                    source: startNode.id,
                    target: endNode.id,
                    type: 'line',
                    // label: '代码',
                    style: {
                        endArrow: {
                            path: "M 0,0 L 8,4 L 7,0 L 8,-4 Z",
                            fill: "#aab7c3",
                            stroke: "#aab7c3"
                        },
                        stroke: "#aab7c3"
                    },
                    // 该边连入 source 点的第 0 个 anchorPoint，
                    sourceAnchor: startNode.point,
                    // 该边连入 target 点的第 0 个 anchorPoint，
                    targetAnchor: endNode.point,
                }

                await graph.addItem("edge", edge as any, true)
            }
        }

        const clientX = event.clientX
        const clientY = event.clientY
        // 获取鼠标在画布的坐标
        const targetLocation = graph.getPointByClient(clientX, clientY)
        // 获取拖拽节点的文本内容
        const label = listMap.get(listValue)
        // 获取一个uuid
        const uid = getUId()
        // 设置节点的model信息
        const d = {
            id: uid,
            value: listValue,
            x: targetLocation.x,
            y: targetLocation.y,
            type: 'monitorNode',
            label: label,
            setNodeStartPoint,
            setNodeEndPoint
        }

        const target = _.cloneDeep(graphData)
        target.nodes = [ ...target.nodes, d ]
        graph.addItem("node", d, true)
    }

    const renderGraph = useCallback(() => {
        if (!graph) return
        graph.clear() // 清除画布
        graph.data(graphData) // 传递数据
        graph.render() // 渲染画布
    }, [graphData, graph, listValue])

    useEffect(() => {
        graph && renderGraph()
    }, [graph])

    const providerValue = {
        setListValue
    }

    return (
        <div className={styles.monitorBox}>
            <PageHead title={"全局监控"}/>
            <Provider value={providerValue}>
                <div
                    className={styles.monitor}
                    onContextMenu={contextRight}
                >
                    <List data={listData} />
                    <div
                        onDrop={painDrop}
                        id={"container"}
                        ref={containerRef}
                        className={styles.containerBox}
                    >
                        <ContextMenu ref={contextMenuRef} />
                    </div>
                    <Explain/>
                </div>
            </Provider>
        </div>
    )
}

type ContextMenuItem = {
    label: string
    icon?: string
    uuid?: number
}

const ContextMenu = forwardRef((props: any, ref) => {
    const menuRef = useRef<HTMLDivElement>()
    const options = [
        { label: "菜单", icon: "", uuid: getUId() },
        { label: "菜单", icon: "", uuid: getUId() },
        { label: "菜单", icon: "", uuid: getUId() },
    ]

    useImperativeHandle(ref, () => ({
        contextMenuDOM: menuRef.current
    }))

    return (
        <div className={styles.contextMenu} ref={menuRef}>
            {options.map((item: ContextMenuItem) => {
                return (
                    <div key={item.uuid} className={styles.contextMenuItem}>
                        <Icon className={"amin-diannao"} />
                        &nbsp;&nbsp;
                        {item.label}
                    </div>
                )
            })}
        </div>
    )
})

export default Monitor
